package fox.framework.rpc.client.invocation.proxy;


import fox.framework.rpc.client.RpcClient;
import fox.framework.rpc.client.invocation.ConsumeInvoker;
import fox.framework.rpc.client.invocation.ConsumeInvokerChain;
import fox.framework.rpc.context.RpcProxySupport;
import fox.framework.rpc.context.definition.RpcDefinition;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Rpc代理抽象类
 *
 * @author cuichao
 * @Description: RpcProxy
 * @Date: create in 2021/1/15 22:14
 */
public abstract class RpcProxy {
    private RpcDefinition definition;

    public RpcProxy(RpcDefinition definition) {
        this.definition = definition;
    }

    protected ConsumeInvokerChain getConsumeChain() {
        //run-time Invokers
        return new RpcProxyConsumeChain(RpcProxySupport.getRegisteredProxyInvoker());
    }

    protected RpcDefinition getDefinition() {
        return definition;
    }

    /**
     * 获取代理对象
     *
     * @return
     */
    public abstract Object getProxy();

    /**
     * 获取代理对象
     *
     * @param loader
     * @return
     */
    public abstract Object getProxy(ClassLoader loader);

    /**
     * Rpc请求调用链
     */
    private class RpcProxyConsumeChain extends ConsumeInvokerChain {

        private Lock cacheLock = new ReentrantLock();
        private final Map<Method, MethodCache> cacheMap = new ConcurrentHashMap<>();

        public RpcProxyConsumeChain() {
            super();
        }

        public RpcProxyConsumeChain(List<ConsumeInvoker> chains) {
            super(chains);
        }

        @Override
        protected Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //get metaspace
            String metaspace = definition.metaspace();
            //get cache
            MethodCache cache = getCache(definition, method);
            //execute
            RpcClient client = RpcProxySupport.getRunTimeRpcClient();
            Object object = client.execute(metaspace,cache.getPath(), args, cache.getReturnType());
            return object;
        }


        private MethodCache getCache(RpcDefinition definition, Method method) {

            MethodCache cache = cacheMap.getOrDefault(method, null);
            if (cache != null) {
                return cache;
            }
            cacheLock.lock();
            try {
                cache = createCache(definition, method);
                cacheMap.put(method, cache);
                return cache;
            } finally {
                cacheLock.unlock();
            }
        }

        private MethodCache createCache(RpcDefinition definition, Method method) {
            String path = String.format("/%s/%s", definition.metaspace(), method.getName());
            return new MethodCache(path,method,method.getName(),method.getGenericReturnType());
        }

    }

    private class MethodCache {
        private String path;
        private Method method;
        private String methodName;
        private Type returnType;

        public MethodCache(String path, Method method, String methodName, Type returnType) {
            this.path = path;
            this.method = method;
            this.methodName = methodName;
            this.returnType = returnType;
        }


        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }


        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public String getMethodName() {
            return methodName;
        }

        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }

        public Type getReturnType() {
            return returnType;
        }

        public void setReturnType(Type returnType) {
            this.returnType = returnType;
        }
    }

}
